﻿
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.Text.RegularExpressions;
using System.Windows.Media.Imaging;
using System.IO;

namespace BitmapTagger
{
	/// <summary>
	/// Represents the tags of a bitmap
	/// </summary>
	public abstract class BitmapTagger
	{

		/// <summary>
		/// Amount used for padding - 4k
		/// </summary>
		private const uint paddingAmount = 4096;

		/// <summary>
		/// Get the default amount used for padding - 4k
		/// </summary>
		protected uint PaddingAmount
		{
			get { return paddingAmount; }
		}

		/// <summary>
		/// Save the Image to the same file path it was loaded from
		/// </summary>
		/// <remarks>
		/// Defaults to save tags to Exif, XMPExif, and XMP
		/// The thread that this is called from must be marked as a STAThread or
		/// this will throw a COM error.
		/// </remarks>
		public abstract void Save();

		/// <summary>
		/// Save the Image to the same file path it was loaded from
		/// </summary>
		/// <param name="saveExif">Whether or not to save the information to Exif tags</param>
		/// <param name="saveXMPExif">Whether or not to save the information to XMPExif tags</param>
		/// <param name="saveXMP">Whether or not to save the information to XMP tags</param>
		/// <remarks>
		/// The thread that this is called from must be marked as a STAThread or
		/// this will throw a COM error.
		/// </remarks>
		public abstract void Save(bool saveExif, bool saveXMPExif, bool saveXMP);

		/// <summary>
		/// Save the Image 
		/// </summary>
		/// <param name="filePath">The file path to save to</param>
		/// <remarks>
		/// The thread that this is called from must be marked as a STAThread or
		/// this will throw a COM error.
		/// </remarks>
		public abstract void Save(string filePath);


		/// <summary>
		/// Save the Image 
		/// </summary>
		/// <param name="filePath">The file path to save to</param>
		/// <param name="saveExif">Whether or not to save the information to Exif tags</param>
		/// <param name="saveXMPExif">Whether or not to save the information to XMPExif tags</param>
		/// <param name="saveXMP">Whether or not to save the information to XMP tags</param>
		/// <remarks>
		/// The thread that this is called from must be marked as a STAThread or
		/// this will throw a COM error.
		/// </remarks>
		public abstract void Save(string filePath, bool saveExif, bool saveXMPExif, bool saveXMP);

		/// <summary>
		/// Convert the Image Tagger's file to another format, the tags will be transfered also
		/// </summary>
		/// <param name="filePath">The filepath to the new image</param>
		/// <param name="transferExif">Whether or not to transfer the Exif data</param>
		/// <param name="transferXMPExif">Whether or not to transfer the XMP Exif data</param>
		/// <param name="transferXMP">Whether or not to transfer the XMP data</param>
		/// <returns>The Image Tagger for the new image</returns>
		/// <remarks>
		/// If filepath's extension is the same format as the current file path, this method is
		/// the same as the Save() method.
		/// The thread that this is called from must be marked as a STAThread or
		/// this will throw a COM error.
		/// </remarks>
		public BitmapTagger ConvertImageFormat(string filePath, bool transferExif, 
			bool transferXMPExif, bool transferXMP)
		{
			string extension = Path.GetExtension(filePath);
			Debug.Assert(ExtensionChecker.IsJPEGExtension(extension) ||
				ExtensionChecker.IsPNGExtension(extension), "filepath must be either jpeg or png to convert");
			string originalExtension = Path.GetExtension(FilePath);

			if (ExtensionChecker.IsJPEGExtension(extension) && ExtensionChecker.IsJPEGExtension(originalExtension) ||
				ExtensionChecker.IsPNGExtension(extension) && ExtensionChecker.IsPNGExtension(originalExtension))
			{
				Save(filePath, transferExif, transferXMPExif, transferXMP);
				return this;
			}

			ImageConverter converter = new ImageConverter();
			return converter.ConvertImageType(this, filePath, transferExif, transferXMPExif, transferXMP);
		}

		/// <summary>
		/// Load an Image
		/// </summary>
		/// <remarks>
		/// Defaults all tags to be loaded with a precedence of Exif > XMPExif > XMP
		/// </remarks>
		/// <param name="filePath">The file path of the Image to load</param>
		public abstract void Load(string filePath);

		/// <summary>
		/// Load an Image
		/// </summary>
		/// <param name="filePath">The file path of the Image to load</param>
		/// <param name="loadExif">Whether or not to load Exif tags</param>
		/// <param name="loadXMPExif">Whether or not to load XMPExif tags</param>
		/// <param name="loadXMP">Whether or not to load XMP tags</param>
		/// <param name="precendenceOfTags">The precendece of the tagging systems (used for conflicts)</param>
		public abstract void Load(string filePath, bool loadExif, bool loadXMPExif, bool loadXMP,
			TagPrecedence precendenceOfTags);

		/// <summary>
		/// Create a new image tagger
		/// </summary>
		/// <param name="filePath">The file path to the image</param>
		/// <param name="loadExif">Whether or not to load Exif tags</param>
		/// <param name="loadXMPExif">Whether or not to load XMPExif tags</param>
		/// <param name="loadXMP">Whether or not to load XMP tags</param>
		/// <param name="precedence">The precedence of the different tagging systems (Exif, XMPExif, XMP)</param>
		/// <returns>The image tagger for the image</returns>
		public static BitmapTagger Create(string filePath, bool loadExif, bool loadXMPExif, bool loadXMP, TagPrecedence precedence)
		{
			var extension = System.IO.Path.GetExtension(filePath);
			if (ExtensionChecker.IsJPEGExtension(extension))
				return new JpegTagger(filePath, loadExif, loadXMPExif, loadXMP, precedence);
			else if(ExtensionChecker.IsPNGExtension(extension))
				return new PngTagger(filePath, loadXMPExif, loadXMP, precedence);
			throw new NotImplementedException("Unknown image type");
		}


		/// <summary>
		/// Create a new image tagger
		/// </summary>
		/// <remarks>
		/// Defaults with loading all tags
		/// Defaults with a Tagging System precedence of Exif > XMPExif > XMP
		/// </remarks>
		/// <param name="filePath">The file path to the image</param>
		/// <returns>The image tagger for the image</returns>
		public static BitmapTagger Create(string filePath)
		{
			return Create(filePath, true, true, true, TagPrecedence.Exif_XMPExif_XMP);
		}

		

		/// <summary>
		/// The file path to the image
		/// </summary>
		private string filePath;

		/// <summary>
		/// Get the file path to the image
		/// </summary>
		public string FilePath
		{
			get { return filePath; }
			protected set 
			{
				Debug.Assert(IsValidPath(value), "That is not a valid file path");
				filePath = value; 
			}
		}

		/// <summary>
		/// The Precedence of the different tagging systems
		/// </summary>
		private TagPrecedence precedence;

		/// <summary>
		/// Get or set the precedence of the different tagging systems
		/// </summary>
		protected TagPrecedence Precedence
		{
			get { return precedence; }
			set { precedence = value; }
		}

		/// <summary>
		/// Get the bitmap creation options to use when loading the images
		/// </summary>
		protected BitmapCreateOptions BitmapOptions
		{
			get { return BitmapCreateOptions.PreservePixelFormat | BitmapCreateOptions.IgnoreColorProfile; }
		}
		

		/// <summary>
		/// Gets whether the specified path is a valid absolute file path.
		/// </summary>
		/// <param name="path">Any path. OK if null or empty.</param>
		/// <returns>True if path is a valid file path, false if not</returns>
		private bool IsValidPath(string path)
		{
			Regex r = new Regex(@"^(([a-zA-Z]\:)|(\\))(\\{1}|((\\{1})[^\\]([^/:*?<>""|]*))+)$");
			return r.IsMatch(path);
		}


		#region image property tags


		/// <summary>
		/// The height of the image
		/// </summary>
		private uint imageHeight;

		/// <summary>
		/// Get the height of the image in pixels
		/// </summary>
		/// <ExifDefinition>
		/// The number of rows of image data. In JPEG compressed 
		/// data a JPEG marker is used instead of this tag. 
		/// </ExifDefinition>
		public uint ImageHeight 
		{
			get { return imageHeight; }
			protected set { imageHeight = value; }
		}

		/// <summary>
		/// The width of the image
		/// </summary>
		private uint imageWidth;

		/// <summary>
		/// Get the width of the image in pixels
		/// </summary>
		/// <ExifDefinition>
		/// The number of columns of image data, equal to the number of 
		/// pixels per row. In JPEG compressed data a JPEG marker is used 
		/// instead of this tag.   
		/// </ExifDefinition>
		public uint ImageWidth
		{
			get { return imageWidth; }
			protected set { imageWidth = value; }
		}



		/// <summary>
		/// The X resolution of the image
		/// </summary>
		private URational xResolution;

		/// <summary>
		/// Get the X Resolution of the image
		/// </summary>
		/// <ExifDefinition>
		/// The number of pixels per ResolutionUnit in the ImageWidth direction. 
		/// When the image resolution is unknown, 72 [dpi] is designated. 
		/// </ExifDefinition>
		/// <value>
		/// Default = 72
		/// </value>
		public URational XResolution
		{
			get { return xResolution; }
			protected set { xResolution = value; }
		}

		/// <summary>
		/// The Y resolution of the image
		/// </summary>
		private URational yResolution;

		/// <summary>
		/// Get the Y Resolution of the image
		/// </summary>
		/// <ExifDefinition>
		/// The number of pixels per  ResolutionUnit in the ImageLength direction. 
		/// The same value as  XResolution is designated. 
		/// </ExifDefinition>
		/// <value>
		/// Default = 72
		/// </value>
		public URational YResolution
		{
			get { return yResolution; }
			protected set { yResolution = value; }
		}


		/// <summary>
		/// The resolution unit for the image
		/// </summary>
		private ushort? resolutionUnit;

		/// <summary>
		/// Get the resolution unit of the image
		/// </summary>
		/// <ExifDefinition>
		/// The unit for measuring XResolution and YResolution. The same unit 
		/// is used for both XResolution and YResolution. If the image resolution 
		/// in unknown, 2 (inches) is designated. 
		/// </ExifDefinition>
		/// <value>
		/// 2 = inches 
		/// 3 = centimeters 
		/// Other = reserved 
		/// </value>
		public ushort? ResolutionUnit
		{
			get { return resolutionUnit; }
			protected set 
			{
				//Debug.Assert(value == 2 || value == 3, "ResolutionUnit set to invalid value");
				Debug.Assert(ExifValues.Instance.IsValidResolutionUnitValue(value), 
					"ResolutionUnit set to invalid value");
				resolutionUnit = value; 
			}
		}

		/// <summary>
		/// Get the Height of the image in the resolution units.
		/// </summary>
		/// <remarks>
		/// This requires YResolution and ImageHeight to be present
		/// </remarks>
		public float? UnitHeight
		{
			get
			{
				if(YResolution == null)
					return null;

				return (float)(ImageHeight * YResolution.Numerator) / (float)YResolution.Denominator;

			}
		}

		/// <summary>
		/// Get the Width of the image in the resolution units.
		/// </summary>
		/// <remarks>
		/// This requires XResolution and ImageWidth to be present
		/// </remarks>
		public float? UnitWidth
		{
			get
			{
				if (XResolution == null)
					return null;

				return (float)(ImageWidth * XResolution.Numerator) / (float)XResolution.Denominator;

			}
		}


		#endregion

		#region description tags


		/// <summary>
		/// The image description of the image
		/// </summary>
		private string imageDescription;

		/// <summary>
		/// Get or set the description for this image
		/// </summary>
		public string ImageDescription
		{
			get { return imageDescription; }
			set { imageDescription = value; }
		}

		/// <summary>
		/// The maker of the equipment that made this image
		/// </summary>
		private string make;

		/// <summary>
		/// Get or set the maker of the equipment that made this image
		/// </summary>
		/// <note>
		/// This is Exif only, no XMP equivalant exists
		/// </note>
		/// <ExifDefinition>
		/// The manufacturer of the recording equipment. This is the manufacturer of 
		/// the DSC, scanner, video digitizer or other equipment that generated the 
		/// image. When the field is left blank, it is treated as unknown. 
		/// </ExifDefinition>
		public string Make
		{
			get { return make; }
			set { make = value; }
		}

		/// <summary>
		/// The model of the equipment that made this image
		/// </summary>
		private string model;

		/// <summary>
		/// Get or set the model of the equipment that made this image
		/// </summary>
		/// <ExifDefinition>
		/// The model name or model number of the equipment. This is 
		/// the model name of number of the DSC, scanner, video digitizer 
		/// or other equipment that generated the image. When the field 
		/// is left blank, it is treated as unknown. 
		/// </ExifDefinition>
		public string Model
		{
			get { return model; }
			set { model = value; }
		}

		/// <summary>
		/// The software or firmware that generated this image
		/// </summary>
		private string software;

		/// <summary>
		/// Get or set the software or firmware that generated this image
		/// </summary>
		/// <ExifDefinition>
		/// This tag records the name and version of the software or firmware 
		/// of the camera or image input device used togenerate the image. The 
		/// detailed format is not specified, but it is recommended that the example 
		/// shown below be followed. When the field is left blank, it is treated as unknown. 
		/// </ExifDefinition>
		/// <example>
		/// "Exif Software Version 1.00a" 
		/// </example>
		public string Software 
		{
			get { return software; }
			set { software = value; }
		}

		/// <summary>
		/// The name of the image creator
		/// </summary>
		private string artist;

		/// <summary>
		/// Get or set the image creator
		/// </summary>
		/// <ExifDefinition>
		/// This tag records the name of the camera owner, photographer or image creator. 
		/// The detailed format is not specified, but it is recommended that 
		/// the information be written as in the example below for ease of 
		/// Interoperability. When the field is left blank, it is treated as unknown. 
		/// </ExifDefinition>
		/// <example>
		/// "Camera owner, John Smith; Photographer, Michael Brown; Image creator, Ken James" 
		/// </example>
		public string Artist
		{
			get { return artist; }
			set { artist = value; }
		}


		/// <summary>
		/// The copyrigt information of this image
		/// </summary>
		private string copyright;

		/// <summary>
		/// Get or set the copyright information of this image
		/// </summary>
		/// <ExifDefinition>
		/// Copyright information. In this standard the tag is used to 
		/// indicate both the photographer and editor copyrights. It is the 
		/// copyright notice of the person or organization claiming rights 
		/// to the image. The Interoperability copyright statement including 
		/// date and rights should be written in  this field; e.g., 
		/// "Copyright, John Smith, 19xx. All rights reserved." In this 
		/// standard the field records both the photographer and editor copyrights, 
		/// with each recorded in a separate part of the statement. When there is a 
		/// clear distinction between the photographer and editor copyrights, these 
		/// are to be written in the order of photographer followed by editor copyright, 
		/// separated by NULL (in this case, since the statement also ends with a NULL, 
		/// there are two NULL codes) (see example 1). When only the photographer copyright 
		/// is given, it  is terminated by one NULL code (see example 2). When only the 
		/// editor copyright is given, the photographer copyright part consists of one space 
		/// followed by a terminating NULL code, then the editor copyright is given (see example 3). 
		/// When the field is left blank, it is treated as unknown. 
		/// </ExifDefinition>
		/// <example>
		/// When both the photographer copyright and editor copyright are given.   
		/// Photographer copyright + NULL[00.H] + editor copyright + NULL[00.H] 
		/// </example>
		/// <example>
		/// Ex. 2) When only the photographer copyright is given. 
		/// Photographer copyright + NULL[00.H] 
		/// </example>
		/// <example>
		/// Ex. 3) When only the editor copyright is given. 
		/// Space[20.H]+ NULL[00.H] + editor copyright + NULL[00.H] 
		/// </example>
		public string Copyright
		{
			get { return copyright; }
			set { copyright = value; }
		}	


		/// <summary>
		/// User keywords or comments on the image
		/// </summary>
		private string userComment;

		/// <summary>
		/// Get or set the user keywords or comments on the image
		/// </summary>
		/// <ExifDefinition>
		/// A tag for Exif users to write keywords or comments on the image 
		/// besides those in ImageDescription, and without the character 
		/// code limitations of the ImageDescription tag.   
		/// </ExifDefinition>
		public string UserComment
		{
			get { return userComment; }
			set { userComment = value; }
		}

		/// <summary>
		/// The Manufacturer note for this image
		/// </summary>
		private string makerNote;

		/// <summary>
		/// Get or set the Manufacturer note for this image
		/// </summary>
		public string MakerNote
		{
			get { return makerNote; }
			set { makerNote = value; }
		}

		#endregion

		#region date and time tags

		/// <summary>
		/// The datetime of image creation
		/// </summary>
		private DateTime? dateTime;

		/// <summary>
		/// Get or set the datetime of image creation/modify
		/// </summary>
		/// <ExifDefinition>
		/// The date and time of image creation. In this standard it  is the 
		/// date and time the file was changed. The format is 
		/// "YYYY:MM:DD HH:MM:SS" with time shown in 24-hour format, and the date and time 
		/// separated by one blank character [20.H]. When the date and time are 
		/// unknown, all  the character spaces except colons (":") may be filled 
		/// with blank characters, or else the Interoperability  field may be 
		/// filled with blank  characters. The character string length is 20 bytes 
		/// including NULL for termination. When the field is left blank, it is treated as unknown.   
		/// </ExifDefinition>
		/// <XmpDefinition>
		/// This is the datetime of last modification
		/// </XmpDefinition>
		public DateTime? DateTime
		{
			get { return dateTime; }
			set { dateTime = value; }
		}

		/// <summary>
		/// The date and time the original image was generated
		/// </summary>
		private DateTime? dateTimeOriginal;

		/// <summary>
		/// Get or set the date and time the original image was generated
		/// </summary>
		/// <ExifDefinition>
		/// The date and time when the original image data was generated. 
		/// For a DSC the date and time the picture was taken are recorded. 
		/// The format is "YYYY:MM:DD HH:MM:SS" with time shown in 24-hour format, 
		/// and the date and time separated by one blank character [20.H]. When 
		/// the date and  time are unknown, all the character spaces except colons (":") 
		/// may be filled with blank characters, or else the Interoperability field may 
		/// be filled with blank characters. 
		/// The character string length is 20 bytes including NULL for termination. 
		/// When the field is left blank, it is treated as unknown.  
		/// </ExifDefinition>
		public DateTime? DateTimeOriginal
		{
			get { return dateTimeOriginal; }
			set { dateTimeOriginal = value; }
		}

		/// <summary>
		/// The date and time the image was stored as digital
		/// </summary>
		private DateTime? dateTimeDigitized;

		/// <summary>
		/// Get or set the date and time the image was stored as digital
		/// </summary>
		public DateTime? DateTimeDigitized
		{
			get { return dateTimeDigitized; }
			set { dateTimeDigitized = value; }
		}


		#endregion

		#region picture-taking condition tags


		/// <summary>
		/// The metering mode used for the image
		/// </summary>
		private ushort? meteringMode;

		/// <summary>
		/// Get or set the metering mode used for the image
		/// </summary>
		/// <values>
		/// 0 = unknown 
		/// 1 = Average 
		/// 2 = CenterWeightedAverage 
		/// 3 = Spot 
		/// 4 = MultiSpot 
		/// 5 = Pattern 
		/// 6 = Partial 
		/// Other = reserved 
		/// 255 = other 
		/// </values>
		public ushort? MeteringMode
		{
			get { return meteringMode; }
			set 
			{
				//Debug.Assert(value == null || value == 0 || value == 1 || 
				//    value == 2 || value == 3 || value == 4 ||
				//    value == 5 || value == 6 || value == 255,
				//    "Metering Mode set to invalid value, value must be 0, 1, 2, 3, 4, 5, 6, 255, or null");
				Debug.Assert(ExifValues.Instance.IsValidMeteringModeValue(value),
					"Metering Mode was not set to a valid value");
				
				meteringMode = value; 
			}
		}


		/// <summary>
		/// The exposure time in seconds
		/// </summary>
		private URational exposureTime;


		/// <summary>
		/// Get or set the exposure time in seconds
		/// </summary>
		/// <ExifDefinition>
		/// Exposure time, given in seconds (sec). 
		/// </ExifDefinition>
		public URational ExposureTime
		{
			get { return exposureTime; }
			set { exposureTime = value; }
		}

		/// <summary>
		/// The F Number of the when the image was taken
		/// </summary>
		private URational fNumber;

		/// <summary>
		/// Get or set the F Number of when the image was taken
		/// </summary>
		/// <ExifDefinition>
		/// The F number. 
		/// </ExifDefinition>
		public URational FNumber
		{
			get { return fNumber; }
			set { fNumber = value; }
		}

		/// <summary>
		/// The program used by the camera to set exposure when the image
		/// was taken
		/// </summary>
		private ushort? exposureProgram;

		/// <summary>
		/// Get or set the program used by the camera to set exposure when
		/// the image was taken
		/// </summary>
		/// <ExifDefinition>
		/// The class of the program used by the camera to set 
		/// exposure when the picture is taken. The tag values 
		/// are as follows.  
		/// </ExifDefinition>
		/// <value>
		/// 0 = Not defined 
		/// 1 = Manual 
		/// 2 = Normal program 
		/// 3 = Aperture priority 
		/// 4 = Shutter priority 
		/// 5 =  Creative program (biased toward depth of field) 
		/// 6 =  Action program (biased toward fast shutter speed) 
		/// 7 =  Portrait mode (for closeup photos with the background out of focus)   
		/// 8 =  Landscape mode (for landscape photos with the background in focus) 
		/// Other = reserved 
		/// </value>
		public ushort? ExposureProgram
		{
			get { return exposureProgram; }
			set 
			{
				//Debug.Assert(value == null || value == 0 || value == 1 || 
				//    value == 2 || value == 3 ||value == 4 || value == 5 || 
				//    value == 6 || value == 7 || value == 8,
				//    "Exposure Program set to invalid value, value must be 0, 1, 2, 3, 4, 5, 6, 7, 8, or null");
				Debug.Assert(ExifValues.Instance.IsValidExposureProgramValue(value),
					"Exposure Program was not set to a valid value");
				
				exposureProgram = value; 
			}
		}



		/// <summary>
		/// The ISO Speed and ISO Latitude of the camera
		/// </summary>
		private ushort? isoSpeedRatings;

		/// <summary>
		/// Get or set the ISO Speed and ISO Latitude of the camera
		/// </summary>
		/// <ExifDefinition>
		/// Indicates the ISO Speed and ISO Latitude of the 
		/// camera or input device as specified in ISO 12232. 
		/// </ExifDefinition>
		public ushort? ISOSpeedRatings
		{
			get { return isoSpeedRatings; }
			set { isoSpeedRatings = value; }
		}

		/// <summary>
		/// The shutter speed of the camera when the image was generated
		/// </summary>
		private Rational shutterSpeed;

		/// <summary>
		/// Get or set the shutter speed of the camera when the image was generated
		/// </summary>
		/// <ExifDefinition>
		/// Shutter speed. The unit is the APEX (
		/// Additive System of Photographic Exposure) setting 
		/// (see Annex C of Exif 2.2 specification).   
		/// </ExifDefinition>
		public Rational ShutterSpeed
		{
			get { return shutterSpeed; }
			set { shutterSpeed = value; }
		}

		/// <summary>
		/// The aperture of the lens from the camera when this image was generated
		/// </summary>
		private URational aperture;

		/// <summary>
		/// Get or set the aperture
		/// </summary>
		/// <ExifDefinition>
		/// The lens aperture. The unit is the APEX (
		/// Additive System of Photographic Exposure) setting 
		/// (see Annex C of Exif 2.2 specification).  
		/// </ExifDefinition>
		public URational Aperture
		{
			get { return aperture; }
			set { aperture = value; }
		}

		/// <summary>
		/// The brightness of the camera when this image was generated
		/// </summary>
		private Rational brightness;

		/// <summary>
		/// Get or set the brightness of the camera when this image was generated
		/// </summary>
		/// <ExifDefinition>
		/// The value of brightness. The unit is the APEX value. 
		/// Ordinarily it is given in the range of -99.99 to 99.99. Note that 
		/// if the numerator of the recorded value is FFFFFFFF.H, 
		/// Unknown shall be indicated. 
		/// </ExifDefinition>
		public Rational Brightness
		{
			get { return brightness; }
			set 
			{
				if (value == null)
					brightness = value;
				else if (value.Numerator == int.MaxValue)
					brightness = null;
				else
					brightness = value; 
			}
		}


		private ushort? lightSource;

		/// <summary>
		/// Get or set the kind of light source for the image
		/// </summary>
		/// <value>
		/// 0 = unknown 
		/// 1 = Daylight 
		///	2 = Fluorescent 
		///	3  =  Tungsten (incandescent light) 
		///	4 = Flash 
		///	9 = Fine weather 
		///	10 = Cloudy weather 
		///	11 = Shade 
		///	12  =  Daylight fluorescent (D 5700 – 7100K) 
		///	13  =  Day white fluorescent (N 4600 – 5400K) 
		///	14  =  Cool white fluorescent (W 3900 – 4500K) 
		///	15  =  White fluorescent (WW 3200 – 3700K) 
		///	17  =  Standard light A 
		///	18  =  Standard light B 
		///	19  =  Standard light C 
		///	20 = D55 
		///	21 = D65 
		///	22 = D75 
		///	23 = D50 
		///	24  =  ISO studio tungsten 
		///	255  =  other light source 
		///	Other = reserved 
		/// </value>
		public ushort? LightSource
		{
			get { return lightSource; }
			set 
			{
			//    Debug.Assert(value == null || value == 0 || value == 1 || value == 2 || 
			//        value == 3 || value == 4 || value == 9 ||
			//        value == 10 || value == 11 || value == 12 || 
			//        value == 13 || value == 14 || value == 15 ||
			//        value == 17 || value == 18 || value == 19 || 
			//        value == 20 || value == 21 || value == 22 ||
			//        value == 23 || value == 24 || value == 255,
			//        "Light Source was set to an invalid value, value must be 0,1,2,3,4,9,10,11,12,13,14,15,17,18,19,20,21,22, 23, 24, 255, or null");
				Debug.Assert(ExifValues.Instance.IsValidLightSourceValue(value),
					"Light Source was not set to a valid value");
				lightSource = value; 
			}
		}

		/// <summary>
		/// Indicates the image sensor type on the camera or input device
		/// </summary>
		private ushort? sensingMethod;


		/// <summary>
		/// Get or set the Sensing Method
		/// </summary>
		/// <ExifDefinition>
		/// Indicates the image sensor type on the camera or input device
		/// </ExifDefinition>
		/// <value>
		/// 1 = Not defined 
		///	2  =  One-chip color area sensor 
		///	3  =  Two-chip color area sensor 
		///	4  =  Three-chip color area sensor 
		///	5  =  Color sequential area sensor 
		///	7 =  Trilinear sensor 
		///	8  =  Color sequential linear sensor 
		///	Other = reserved 
		/// </value>
		public ushort? SensingMethod
		{
			get { return sensingMethod; }
			set 
			{
				//Debug.Assert(value == null || value == 1 || value == 2 || 
				//    value == 3 || value == 4 || value == 5 || 
				//    value == 7 || value == 8,
				//    "SensingMethod was set with a nonvalid value", "Must be value of 1,2,3,4,5,7, 8, null");
				Debug.Assert(ExifValues.Instance.IsValidSensingMethodValue(value),
					"Sensing Method not set to valid value");
				
				sensingMethod = value; 
			}
		}



		#endregion

		#region datetime helper methods

		/// <summary>
		/// The tag string used for unknown datetimes
		/// </summary>
		private const string unknownDateTime = "    :  :     :  :  ";

		/// <summary>
		/// Get a DateTime? from the DateTime string used in Exif tags
		/// </summary>
		/// <param name="dateTimeFromTags">The DateTime string used in Exif tags</param>
		/// <returns>A DateTime? for the passed string</returns>
		protected DateTime? GetDateTimeFromExifString(string dateTimeFromTags)
		{
			if (dateTimeFromTags.Equals(unknownDateTime))
				return null;
			else
				return (DateTime?)System.DateTime.ParseExact(dateTimeFromTags,
					"yyyy:MM:dd HH:mm:ss", System.Globalization.CultureInfo.InvariantCulture);
		}

		/// <summary>
		/// Get a string for Exif datetime tags from a DateTime?
		/// </summary>
		/// <param name="dateTime">The DateTime to convert</param>
		/// <returns>The DateTime in string form for Exif tagging</returns>
		protected string GetExifTagStringFromDateTime(DateTime? dateTime)
		{
			if (dateTime == null)
				return unknownDateTime;
			else
				return ((DateTime)dateTime).ToString("yyyy:MM:dd HH:mm:ss");
		}

		/// <summary>
		/// Get a string for XMP tags of type DateTime from DateTime?
		/// </summary>
		/// <param name="dateTime">The DateTime to convert</param>
		/// <returns>The DateTime in string form for XMP tagging</returns>
		protected string GetXMPTagStringFromDateTime(DateTime? dateTime)
		{
			if (dateTime == null)
				throw new Exception("Cannot convert a null DateTime to an XMP tag string");

			DateTimeOffset offset = new DateTimeOffset((DateTime)dateTime);
			return offset.ToString("yyyy-MM-ddTHH:mm:ssZ");
		}

		/// <summary>
		/// Get a DateTime? from the DateTime string used in XMP tags
		/// </summary>
		/// <param name="dateTimeFromTags">The DateTime string used in XMP tags</param>
		/// <returns>A DateTime? for the passed string</returns>
		protected DateTime? GetDateTimeFromXMPString(string dateTimeFromTags)
		{
			//If only a length of four only the year as was passed
			if(dateTimeFromTags.Length == 4)
			{
				int year = int.Parse(dateTimeFromTags);
				return new DateTime(year, 1, 1);
			}
		
			return DateTimeOffset.Parse(dateTimeFromTags).DateTime;
		}

		/// <summary>
		/// For tags that may be sequences return it as a string.
		/// </summary>
		/// <param name="sequence">The possible sequence of string</param>
		/// <returns>The sequence represented as a string</returns>
		protected string GetStringFromPossibleSequence(object sequence)
		{
			if (sequence is string)
				return sequence as string;
			else if (sequence is string[])
			{
				string converted = "";
				string[] stringArray = sequence as string[];
				foreach (string s in stringArray)
					converted += s;
				return converted;
			}
			throw new Exception("object type must be string or string[] in GetStringFromPossibleSequence");
		}

		#endregion


		/// <summary>
		/// Copy all tag values from one image tagger object to another
		/// </summary>
		/// <param name="destination">The image tagger to copy the tags to</param>
		public virtual void CopyTags(BitmapTagger destination)
		{
			Debug.Assert(destination != null, "destination cannot be null when copying tags");

			destination.Aperture = this.Aperture;
			destination.Artist = this.Artist;
			destination.Brightness = this.Brightness;
			destination.Copyright = this.Copyright;
			destination.DateTime = this.DateTime;
			destination.DateTimeDigitized = this.DateTimeDigitized;
			destination.DateTimeOriginal = this.DateTimeOriginal;
			destination.ExposureProgram = this.ExposureProgram;
			destination.ExposureTime = this.ExposureTime;
			destination.FNumber = this.FNumber;
			destination.ImageDescription = this.ImageDescription;
			destination.ISOSpeedRatings = this.ISOSpeedRatings;
			destination.LightSource = this.LightSource;
			destination.Make = this.Make;
			destination.MakerNote = this.MakerNote;
			destination.MeteringMode = this.MeteringMode;
			destination.Model = this.Model;
			destination.ResolutionUnit = this.ResolutionUnit;
			destination.SensingMethod = this.SensingMethod;
			destination.ShutterSpeed = this.ShutterSpeed;
			destination.Software = this.Software;
			destination.UserComment = this.UserComment;
			destination.XResolution = this.XResolution;
			destination.YResolution = this.YResolution;
		}
	}
}
